/*
Copyright (C) 2022-2024 Inspur Corp.

SPDX-License-Identifier: Apache-2.0
*/
package service

import (
	"encoding/hex"
	"encoding/json"
	"fmt"
	"github.com/avast/retry-go"
	"github.com/tidwall/gjson"
	"icep/bean/entity"
	"icep/bean/model"
	"icep/bean/param"
	"icep/chain/client"
	chm "icep/chain/model"
	"icep/common/constant"
	"icep/common/crypto/gm"
	"icep/common/db"
	"icep/common/file"
	"icep/common/upload"
	"icep/common/utils"
	uuid2 "icep/common/uuid"
	"os"
	"strconv"
	"strings"
	"time"
)

func (is *IcepService) GetDataEvidencePage(userId, evidenceName, useEncrypt, startTime, endTime string, pageSize, pageNo int) (*db.Page, error) {
	args := make([]interface{}, 0)
	ccQuery := utils.StrJoin("user_id = ?")
	args = append(args, userId)

	if len(evidenceName) > 0 {
		ccQuery = utils.StrJoin(ccQuery, " and evidence_name like concat('%', ?::varchar, '%') escape '/'")
		ccArg := strings.Replace(evidenceName, "%", "/%", -1)
		ccArg = strings.Replace(ccArg, "_", "/_", -1)
		args = append(args, ccArg)
	}
	if len(useEncrypt) > 0 {
		ccQuery += " and use_encrypt = ?"
		args = append(args, useEncrypt)
	}
	if startTime != "" {
		ccQuery += " and created_time >= ?"
		args = append(args, startTime)
	}
	if endTime != "" {
		ccQuery += " and created_time <= ?"
		args = append(args, endTime)
	}
	page := db.NewPage(0, pageNo).SetPageSize(pageSize)

	var countEntity entity.IcepDataEvidence
	count, err := is.DbEngine.Where(ccQuery, args...).Count(&countEntity)
	if err != nil {
		log.Errorf("query dataEvidence list count error: %s", err)
		return nil, err
	}
	page.SetTotal(count)

	dataEvidenceList := make([]*entity.IcepDataEvidence, 0)
	err = is.DbEngine.Where(ccQuery, args...).Limit(pageSize, (pageNo-1)*pageSize).Desc("updated_time").Find(&dataEvidenceList)
	if err != nil {
		log.Errorf("query func GetDataEvidencePage error: %s", err)
		return nil, err
	}
	dataEDetailList := make([]*model.DataEvidenceDetail, 0)
	for _, dataE := range dataEvidenceList {
		dataEDetailList = append(dataEDetailList, &model.DataEvidenceDetail{
			Id:           dataE.Id,
			UserId:       dataE.UserId,
			IdentityId:   dataE.IdentityId,
			EvidenceName: dataE.EvidenceName,
			UseEncrypt:   dataE.UseEncrypt,
			Status:       dataE.Status,
			TxId:         dataE.TxId,
			TxTime:       dataE.TxTime,
			CreatedTime:  dataE.CreatedTime.Format(constant.TimeLayout),
			UpdatedTime:  dataE.UpdatedTime.Format(constant.TimeLayout),
		})
	}
	page.CreatePage(dataEDetailList)
	return page, err
}

func (is *IcepService) GetDataEvidenceList(userId string, evidenceParam param.DataEvidenceParam) ([]*entity.IcepDataEvidence, error) {
	dataEvidenceList := make([]*entity.IcepDataEvidence, 0)
	err := is.DbEngine.Where("evidence_name = ? ", evidenceParam.EvidenceName).Desc("updated_time").Find(&dataEvidenceList)
	if err != nil {
		log.Errorf("query func GetDataEvidenceList error: %s", err)
		return nil, err
	}
	return dataEvidenceList, err
}

func (is *IcepService) GetDataEvidenceInfo(userId, tenantId string, evidenceId string) (*model.DataEvidenceInfo, error) {
	dataEvidence := new(entity.IcepDataEvidence)
	has, err := is.DbEngine.Where("id = ? and user_id = ?", evidenceId, userId).Get(dataEvidence)
	if err != nil {
		log.Errorf("存证编号%s查询异常 %v", evidenceId, err)
		return nil, err
	}
	dataEvidenceInfo := &model.DataEvidenceInfo{
		EvidenceId:   dataEvidence.Id,
		EvidenceName: dataEvidence.EvidenceName,
		UseEncrypt:   dataEvidence.UseEncrypt,
		Status:       dataEvidence.Status,
		EvidenceCert: dataEvidence.EvidenceCert,
		TxId:         dataEvidence.TxId,
		TxTime:       dataEvidence.TxTime,
	}
	if has {
		txId := dataEvidence.TxId
		//get data by chain txid
		client := is.GetChainClient(tenantId)
		blockInfo := client.GetBlockInfoById(dataEvidence.TxId)
		txInfo := client.GetTransactionInfoById(dataEvidence.TxId)
		log.Infof("%s's blockinfo is: %v ----- txinfo is :%v ", txId, blockInfo, txInfo)
		txContent := new(chm.WriteModel)
		json.Unmarshal([]byte(txInfo.TxContent), txContent)
		chainData := new(model.ChainDataEvidence)
		json.Unmarshal([]byte(txContent.Value), chainData)

		dataEvidenceInfo.BlockNum = fmt.Sprintf("%d", blockInfo.BlockNum)
		dataEvidenceInfo.BlockHash = blockInfo.BlockHash
		dataEvidenceInfo.TxContent = txInfo.TxContent
		dataEvidenceInfo.EvidenceData = chainData.EvidenceData
		dataEvidenceInfo.Signature = chainData.Sign
		dataEvidenceInfo.PubKey = chainData.PubKey

		return dataEvidenceInfo, err
	}
	return dataEvidenceInfo, nil
}

func (is *IcepService) CreateDataEvidence(userId string, param param.DataEvidenceParam) error {
	user, result := is.GetUserByUserId(userId)
	if !result {
		log.Error("用户私钥获取失败")
	}
	evidenceId := uuid2.GenerateAlphaWithPrefix("dE")
	userPrvK := gm.DecryptPrvKey(user.PrvKey)
	putValue := make(map[string]string, 8)

	//record dataEvidence
	identity, _ := is.GetIdentityByTenantId(user.TenantId)
	dataEvidence := entity.IcepDataEvidence{
		Id:           evidenceId,
		UserId:       userId,
		IdentityId:   identity.IdentityId,
		EvidenceName: param.EvidenceName,
		UseEncrypt:   param.UseEncrypt,
		Status:       "0",
		TxId:         "",
		TxTime:       "",
		CreatedTime:  time.Now(),
		UpdatedTime:  time.Now(),
	}
	_, err := is.DbEngine.InsertOne(&dataEvidence)
	if err != nil {
		log.Error("数据存证记录插入异常")
		return err
	}

	//record signature
	icepSignature := entity.IcepSignature{
		Id:          uuid2.GenerateAlpha(),
		EvidenceId:  evidenceId,
		Signature:   "",
		CreatedTime: time.Now(),
		UpdatedTime: time.Now(),
	}
	//加密、签名
	if param.UseEncrypt == "1" {
		sm4Key := gm.GenerateSm4Key()
		sm4KeyBytes, _ := hex.DecodeString(sm4Key)
		encrytSecret := gm.EncryptByPubKey(user.PubKey, sm4KeyBytes)
		icepSecret := entity.IcepSecret{
			Id:            uuid2.GenerateAlpha(),
			EvidenceId:    evidenceId,
			UserId:        userId,
			EncryptSecret: encrytSecret,
			CreatedTime:   time.Now(),
			UpdatedTime:   time.Now(),
		}
		_, err := is.DbEngine.InsertOne(&icepSecret)
		if err != nil {
			log.Error("对称秘钥记录插入异常")
			return err
		}
		param.EncryptedData = hex.EncodeToString(gm.Sm4Encrypt(sm4Key, []byte(param.EvidenceData)))
		icepSignature.Signature = gm.Sign(userPrvK, []byte(param.EncryptedData))
		putValue["evidenceData"] = param.EncryptedData
	} else {
		icepSignature.Signature = gm.Sign(userPrvK, []byte(param.EvidenceData))
		putValue["evidenceData"] = param.EvidenceData
	}
	_, err = is.DbEngine.InsertOne(&icepSignature)
	if err != nil {
		log.Error("签名记录插入异常")
		return err
	}

	//线程判断存证是否成功
	if user.TenantId == "" {
		log.Infof("%s's TenantId is null: ", user.Id)
	}
	client := is.GetChainClient(user.TenantId)
	putValue["sign"] = icepSignature.Signature
	putValue["userId"] = userId
	putValue["pubKey"] = user.PubKey
	value, err := json.MarshalIndent(putValue, "", " ")
	if err != nil {
		log.Errorf("上链数据格式异常： %v", err)
		return err
	}
	go is.dataEvidenceToChain(client, dataEvidence, string(value))

	return err
}

func (is *IcepService) dataEvidenceToChain(client *client.IchainClient, dataEvidence entity.IcepDataEvidence, value string) {
	var err error
	// 定义一个重试策略
	retryStrategy := []retry.Option{
		retry.Delay(10 * time.Millisecond),
		retry.Attempts(2),
		retry.LastErrorOnly(true),
	}
	evidenceId := dataEvidence.Id
	// 使用重试策略进行重试
	log.Infof("%s's putDataEvidence is : %s", evidenceId, value)
	err = retry.Do(
		func() error {
			txId, payload := client.PutDataEvidence(evidenceId, value)
			log.Infof("txId: %s, payload: %s", txId, payload)
			t1, err := strconv.ParseBool(gjson.Get(payload, "success").Raw)
			if err != nil {
				log.Errorf("payload获取success字段值 err is : %v: ", err)
			}
			if t1 {
				txInfo := client.GetTransactionInfoById(txId)
				blockInfo := client.GetBlockInfoById(txId)

				letterParam := is.GetDepositoryLetterParam(dataEvidence.UserId)
				letterParam.EvidenceId = dataEvidence.Id
				letterParam.EvidenceType = "数据存证"
				letterParam.TxId = txId
				letterParam.EvidenceTime = txInfo.TxTime
				letterParam.BlockNum = int(blockInfo.BlockNum)
				letterParam.BlockHash = blockInfo.BlockHash
				tempDir := os.TempDir()
				path := utils.StrJoin(tempDir, constant.FileSeparator, evidenceId, constant.FileSeparator, "存证函.png")
				letterParam.TargetFile = path
				err := utils.CreateEvidenceLetter(letterParam)
				if err != nil {
					log.Errorf("create evidence letter error: %v", err)
					return err
				}

				fileUrl, err := upload.GetUploadClient().UploadFile(path, letterParam.EvidenceId+"/存证函.png")
				if err != nil {
					log.Errorf("fileEvidenceToChain upload file error: %v", err)
					return err
				}
				file.DeleteFile(path)

				dataEvidence.EvidenceCert = fileUrl
				dataEvidence.TxId = txId
				dataEvidence.TxTime = txInfo.TxTime
				dataEvidence.Status = "1"
				dataEvidence.UpdatedTime = time.Now()
				_, err = is.DbEngine.Where("id = ?", evidenceId).Cols("evidence_cert", "tx_id", "tx_time", "status", "updated_time").Update(&dataEvidence)
				if err != nil {
					log.Errorf("%s 更新数据存证 eror %v:  ", evidenceId, err)
					return err
				}
			}
			return err
		},
		retryStrategy...)

	if err != nil {
		log.Errorf("Error occurred after 2 retries")
		dataEvidence.Status = "2"
		dataEvidence.UpdatedTime = time.Now()
		_, err = is.DbEngine.Where("id = ?", evidenceId).Cols("status", "updated_time").Update(&dataEvidence)
		if err != nil {
			log.Errorf("%s 更新数据存证 eror %v:  ", err)
			return
		}
	}
}

//非秘钥托管类数据存证接口
func (is *IcepService) CreateDataNokeyEvidence(userId string, param param.DataEvidenceParam) error {
	user, result := is.GetUserByUserId(userId)
	if !result {
		log.Error("用户获取失败")
	}
	evidenceId := uuid2.GenerateAlphaWithPrefix("dEN")
	//record dataEvidence
	identity, _ := is.GetIdentityByTenantId(user.TenantId)
	dataEvidence := entity.IcepDataEvidence{
		Id:           evidenceId,
		UserId:       userId,
		IdentityId:   identity.IdentityId,
		EvidenceName: param.EvidenceName,
		UseEncrypt:   param.UseEncrypt,
		Status:       "0",
		TxId:         "",
		TxTime:       "",
		CreatedTime:  time.Now(),
		UpdatedTime:  time.Now(),
	}
	_, err := is.DbEngine.InsertOne(&dataEvidence)
	if err != nil {
		log.Error("数据存证记录插入异常")
		return err
	}

	//record signature
	icepSignature := entity.IcepSignature{
		Id:          uuid2.GenerateAlpha(),
		EvidenceId:  evidenceId,
		Signature:   param.Signature,
		CreatedTime: time.Now(),
		UpdatedTime: time.Now(),
	}
	//加密、签名
	if param.UseEncrypt == "1" {
		icepSecret := entity.IcepSecret{
			Id:            uuid2.GenerateAlpha(),
			EvidenceId:    evidenceId,
			UserId:        userId,
			EncryptSecret: param.EncryptedSecret,
			CreatedTime:   time.Now(),
			UpdatedTime:   time.Now(),
		}
		_, err := is.DbEngine.InsertOne(&icepSecret)
		if err != nil {
			log.Error("对称秘钥记录插入异常")
			return err
		}
	}
	_, err = is.DbEngine.InsertOne(&icepSignature)
	if err != nil {
		log.Error("签名记录插入异常")
		return err
	}

	//线程判断存证是否成功
	if user.TenantId == "" {
		log.Infof("%s's TenantId is null: ", user.Id)
	}
	client := is.GetChainClient(user.TenantId)
	putValue := make(map[string]string, 8)
	putValue["sign"] = icepSignature.Signature
	putValue["userId"] = userId
	putValue["pubKey"] = user.PubKey
	value, err := json.MarshalIndent(putValue, "", " ")
	if err != nil {
		log.Errorf("上链数据格式异常： %v", err)
		return err
	}
	go is.dataEvidenceToChain(client, dataEvidence, string(value))

	return err
}

func (is *IcepService) DecryptDataEvidence(userPrivateKey, evidenceId, encryptedData string) (string, bool) {
	decryptSecret := new(entity.IcepSecret)
	result, _ := is.DbEngine.Where("evidence_id = ?", evidenceId).Get(decryptSecret)
	if result {
		sm4Key := gm.DecryptByPrvKey(gm.DecryptPrvKey(userPrivateKey), decryptSecret.EncryptSecret)
		if sm4Key != nil {
			encryptedDataByte, _ := hex.DecodeString(encryptedData)
			originData := gm.Sm4Decrypt(hex.EncodeToString(sm4Key), encryptedDataByte)
			if originData != nil {
				return string(originData), true
			}
		}
	}
	return "", false
}
